<?php

abstract class Common_Chart_Strategy_Google {

    const URL = 'http://chart.apis.google.com/chart';

    const CHART_TYPE_PIE = 'p';
    const CHART_TYPE_PIE_3D = 'p3';
    const CHART_TYPE_LINE = 'lc';
    const CHART_TYPE_LINE_SPARK = 'ls';
    const CHART_TYPE_BAR_VERTICAL_GROUPED = 'bvg';
    const CHART_TYPE_BAR_VERTICAL_STACKED = 'bvs';
    const CHART_TYPE_BAR_HORIZONTAL_GROUPED = 'bhg';
    const CHART_TYPE_BAR_HORIZONTAL_STACKED = 'bhs';
    const CHART_TYPE_RADAR = 'r';
    const CHART_TYPE_ANALOG = 'gom';
    const CHART_TYPE_SCATTER = 's';

    const PARAMETER_CHART_TYPE = 'cht';
    const PARAMETER_CHART_SIZE = 'chs';
    const PARAMETER_CHART_DATA = 'chd';
    const PARAMETER_CHART_LABELS = 'chl';
    const PARAMETER_CHART_MARKERS = 'chm';
    const PARAMETER_CHART_MARGINS = 'chma';
    const PARAMETER_CHART_COLOUR = 'chco';
    const PARAMETER_CHART_DATA_SCALE = 'chds';
    const PARAMETER_CHART_DATA_LABEL = 'chdl';
    const PARAMETER_CHART_AXIS_VISIBLE = 'chxt';
    const PARAMETER_CHART_AXIS_RANGE = 'chxr';
    const PARAMETER_LINE_STYLE = 'chls';
    const PARAMETER_AXIS_STYLE = 'chxs';
    const PARAMETER_CHART_FILL = 'chf';
    const PARAMETER_CHART_LABELS_CUSTOM = 'chxl';
    const PARAMETER_CHART_GRID_LINES = 'chg';
    const PARAMETER_BAR_SPACING = 'chbh';
    const PARAMETER_LEGEND_KEY = 'chdl';
    const PARAMETER_LEGEND_POSITION = 'chdlp';
    const PARAMETER_DATA_ROTATION = 'chp';
    const PARAMETER_AXIS_LABEL_POSITIONS = 'chxp';
    const PARAMETER_CHART_CAPTION = 'chtt';

    const NULL_VALUE = '_';

    private static $arr_config = array();

    private $int_dynamic_height = null;
    private $int_dynamic_width = null;

    private $arr_series = array();
    private $arr_headers = array();

    private $arr_data = array();
    private $int_data_max = null;
    private $int_data_min = null;
    private $int_sampling_size = 1;
    
    private $arr_render_options = array();

    public function __construct() {
        
    }
    
    abstract protected function get_chart_code();
    abstract public function get_chart_type();

    public function get_strategy_type() {
        return Common_Chart_Factory::STRATEGY_GOOGLE;
    }

    public function has_feature($str_feature_key) {
        return false;
    }
    
    public function convert_chart_type($enm_chart_type) {

        $arr_available_conversions = $this->get_available_chart_conversions();
        
        $enm_strategy_type = $this->get_strategy_type();

        $obj_strategy = null;
        $obj_chart_factory = new Common_Chart_Factory();

        if(in_array($enm_chart_type, $arr_available_conversions)) {
            $obj_strategy = $obj_chart_factory->generate_strategy($enm_chart_type, $enm_strategy_type);
        }

        if(!is_null($obj_strategy)) {
            $obj_strategy->bind($this->get_data(), $this->get_headers());
            $obj_strategy->set_series($this->get_series());
        }

        return $obj_strategy;
        
    }
    
    public function get_available_chart_conversions() {
        return array();
    }

    public function get_sampling_size()
    {
        return $this->int_sampling_size;
    }

    //--------------------------------------------------------------------------

    public function bind($arr_data, $arr_headers = array()) {

        $this->set_data($arr_data);
        $this->set_headers($arr_headers);

        // default the data series definitions to the data's keys
        $arr_series = array();
        foreach(array_keys($arr_data) as $str_series_key) {
            $arr_series[$str_series_key] = $str_series_key;
        }

        $this->set_series($arr_series);

    }
    
    public function count_data_points() {
        
        $int_max_points = null;
        
        if(is_array($this->arr_data)) {
            
            // get the data series with the most amount of data points
            $int_max_points = 0;
            foreach($this->arr_data as $str_series_key => $arr) {
                $int_count = count($arr);
                if($int_count > $int_max_points) {
                    $int_max_points = $int_count;
                }
            }
            
        }
        
        return $int_max_points;
    }

    protected function prepare_data($arr_data, $arr_options = array()) {

        $arr_chart_data = array();
    
        $int_min_data = null;
        $int_max_data = null;

        foreach($arr_data as $str_series_key => $arr) {

            $arr_chart_data[$str_series_key] = array();

            foreach($arr as $num) {

                $mix = self::NULL_VALUE;
                if(!is_null($num)) {

                    // google chart only handles values with 5 significant digits
                    if(Utility_NumberFunctions::countDigits($num) > 5) {
                        $num = Utility_NumberFunctions::toPrecision($num, 5);
                    }

                    if(is_null($int_min_data) || ($num < $int_min_data)) {
                        $int_min_data = $num;
                    }
                    if(is_null($int_max_data) || ($num > $int_max_data)) {
                        $int_max_data = $num;
                    }

                    $mix = $num;
                }

                $arr_chart_data[$str_series_key][] = $mix;

            }
        }

        $int_sampling_size = 1;

        if (isset($arr_options['auto_resample']) && ($arr_options['auto_resample'] == true)) {
            $arr_temp = reset($arr_chart_data);
            
            $int_data_size = count($arr_temp);
            while (($int_data_size / $int_sampling_size) > 140) {
                $int_sampling_size++;
            }
            
        } else if(isset($arr_options['resample_data'])) {
            $int_sampling_size = (int) $arr_options['resample_data'];
        }

        if($int_sampling_size > 1) {
            $this->int_sampling_size = $int_sampling_size;
            foreach($arr_chart_data as $str_series_key => $arr) {
                $arr_chart_data[$str_series_key] = Utility_ArrayFunctions::sample($arr, $int_sampling_size);
            }
        }

        $this->set_data_min($int_min_data);
        $this->set_data_max($int_max_data);

        return $arr_chart_data;

    }

    protected function prepare_headers($arr_headers, &$arr_data, $arr_options = array()) {
        
        $arr_data_for_sample_series = array();
        
        // get the data series with the most amount of data points
        $int_max_points = 0;
        foreach($arr_data as $str_series_key => $arr) {
            $int_count = count($arr);
            if($int_count > $int_max_points) {
                $arr_data_for_sample_series = $arr;
                $int_max_points = $int_count;
            }
        }

        foreach($arr_headers as $i => $mix) {
            if(!isset($arr_data_for_sample_series[$i])) {
                unset($arr_headers[$i]);
            }
        }

        return $arr_headers;
    }

    //--------------------------------------------------------------------------

    protected function apply_data_options($arr_chart_parameters, $arr_data, $arr_options = array()) {

        $arr_series_data = array();
        $arr_series_hidden = array();

        $arr_series_to_use = $this->get_series_definitions($arr_options);

        $arr_hidden_keys = array();
        if(isset($arr_options['hide_series'])) {
            $arr_hidden_keys = (array) $arr_options['hide_series'];
        }

        foreach($arr_data as $str_series_key => $arr) {

            if(isset($arr_series_to_use[$str_series_key])) {

                if(in_array($str_series_key, $arr_hidden_keys)) {
                    $arr_series_hidden[$str_series_key] = implode(',', $arr);
                } else {
                    $arr_series_data[$str_series_key] = implode(',', $arr);
                }

            }
            
        }

        $arr_series = array_merge($arr_series_data, $arr_series_hidden);
        
        $int_included_series = null;
        if(!empty($arr_series_hidden)) {
            $int_included_series = count($arr_series_data);
        }

        $str_chart_data = $this->encode_data($arr_series, $int_included_series);

        $arr_chart_parameters[self::PARAMETER_CHART_DATA] = $str_chart_data;

        return $arr_chart_parameters;
    }

    protected function apply_header_options($arr_chart_parameters, $arr_headers, $arr_options = array()) {

        $str_chart_labels = implode('|', $arr_headers);
        $arr_chart_parameters[self::PARAMETER_CHART_LABELS] = $str_chart_labels;

        return $arr_chart_parameters;
    }

    //--------------------------------------------------------------------------

    public function generate_chart_parameters($arr_options = array()) {

        $arr_data = $this->get_data();
        $arr_headers = $this->get_headers();

        if(isset($arr_options['trim_data'])) {
            Common_Utility_ChartFunctions::trim_data($arr_data, $arr_headers);
        }

        $arr_data = $this->prepare_data($arr_data, $arr_options);
        $arr_headers = $this->prepare_headers($arr_headers, $arr_data, $arr_options);

        $arr_chart_parameters = $this->get_chart_parameters();

        $arr_chart_parameters = $this->apply_data_options($arr_chart_parameters, $arr_data, $arr_options);
        $arr_chart_parameters = $this->apply_header_options($arr_chart_parameters, $arr_headers, $arr_options);
        $arr_chart_parameters = $this->apply_render_options($arr_chart_parameters, $arr_data, $arr_headers, $arr_options);
        $arr_chart_parameters = $this->apply_size_options($arr_chart_parameters, $arr_options);

        return $arr_chart_parameters;
    }

    public function get_chart_source($arr_options = array()) {

        $arr_chart_parameters = $this->generate_chart_parameters($arr_options);

        $str_chart_source = $this->get_source($arr_chart_parameters, $arr_options);

        return $str_chart_source;
    }

    public function get_thumbnail_source($arr_options = array()) {

        $arr_chart_parameters = $this->generate_chart_parameters($arr_options);
        $arr_chart_parameters = $this->apply_thumbnail_options($arr_chart_parameters, $arr_options);

        $str_chart_source = $this->get_source($arr_chart_parameters, $arr_options);

        return $str_chart_source;
    }

    protected function get_source($arr_chart_parameters, $arr_options = array()) {

        $str_chart_parameters = http_build_query($arr_chart_parameters, '', '&amp;');
        $str_chart_source = self::URL . '?' . $str_chart_parameters;
        // egg($str_chart_source); 
        /*
        if((strlen($str_chart_source) > 1750) && !isset($arr_options['use_local_source'])) {
            $arr_options['use_local_source'] = true;
        }

        if(!isset($arr_options['use_local_source']) || ($arr_options['use_local_source'] == true)) {
            $str_chart_source = $this->convert_to_local_source($str_chart_source, $arr_options);

            if(isset($arr_options['base_path'])) {
                $str_chart_source = $arr_options['base_path'] . $str_chart_source;
            }
        }*/
        
        return $str_chart_source;
    }

    //--------------------------------------------------------------------------

    protected function apply_render_options($arr_chart_parameters, $arr_data, $arr_headers, $arr_options = array()) {

        $arr_chart_parameters = $this->apply_chart_options($arr_chart_parameters, $arr_data, $arr_headers, $arr_options);
        $arr_chart_parameters = $this->apply_colour_options($arr_chart_parameters, $arr_data, $arr_headers, $arr_options);

        return $arr_chart_parameters;
    }

    protected function apply_chart_options($arr_chart_parameters, $arr_data, $arr_headers, $arr_options = array()) {

        if(isset($arr_options['chart_margins'])) {
            $arr_chart_parameters[self::PARAMETER_CHART_MARGINS] = $arr_options['chart_margins'];
        }

        if(isset($arr_options['background_colour'])) {
            $str_value = 'bg,s,' . $arr_options['background_colour'];
            $arr_chart_parameters = $this->add_chart_fill($arr_chart_parameters, $str_value);
        }

        if(isset($arr_options['chart_colour'])) {
            $str_value = 'c,s,' . $arr_options['chart_colour'];
            $arr_chart_parameters = $this->add_chart_fill($arr_chart_parameters, $str_value);
        }
        
        if(isset($arr_options['embed_caption']) && ($arr_options['embed_caption']) && isset($arr_options['caption'])) {
            $arr_chart_parameters[self::PARAMETER_CHART_CAPTION] = $arr_options['caption'];
        }

        return $arr_chart_parameters;
    }

    protected function apply_colour_options($arr_chart_parameters, $arr_data, $arr_headers, $arr_options = array()) {

        if(isset($arr_options['colours'])) {

            $arr_colours = $arr_options['colours'];

            $arr_chart_colours = array();
            
            $i = 0;
            foreach($arr_data as $str_series_key => $mix) {
                $arr_chart_colours[$str_series_key] = $arr_colours[$i % count($arr_colours)];
                $i++;
            }

            $arr_options['series_colours'] = $arr_chart_colours;
        }

        if(isset($arr_options['series_colours'])) {
            
            $arr_temp = (array) $arr_options['series_colours'];

            $arr_colours = array();
            foreach($arr_data as $str_series_key => $mix) {
                if(isset($arr_temp[$str_series_key])) {
                    $arr_colours[$str_series_key] = $arr_temp[$str_series_key];
                }
            }

            $str_chart_colours = implode($this->get_colour_delimiter(), $arr_colours);
            $arr_chart_parameters[self::PARAMETER_CHART_COLOUR] = $str_chart_colours;
            
        }

        return $arr_chart_parameters;
    }

    protected function get_colour_delimiter() {
        return '|';
    }

    protected function apply_size_options($arr_chart_parameters, $arr_options) {

        $int_width = $this->get_dynamic_width();
        if(!empty($int_width)) {
            if(!isset($arr_options['width']) || ($arr_options['width'] < $int_width)) {
                $arr_options['width'] = $int_width;
            }
        }

        $int_height = $this->get_dynamic_height();
        if(!empty($int_height)) {
            if(!isset($arr_options['height']) || ($arr_options['height'] < $int_height)) {
                $arr_options['height'] = $int_height;
            }
        }

        $str_chart_size = $arr_options['width'] . 'x' . $arr_options['height'];
        $arr_chart_parameters[self::PARAMETER_CHART_SIZE] = $str_chart_size;

        return $arr_chart_parameters;
    }

    protected function apply_thumbnail_options($arr_chart_parameters, $arr_options = array()) {

        $arr_thumbnail_parameters = array();

        $arr_parameters_to_copy = array(
            self::PARAMETER_CHART_SIZE,
            self::PARAMETER_CHART_DATA,
            self::PARAMETER_CHART_DATA_SCALE
        );

        foreach($arr_parameters_to_copy as $str_key) {
            if(isset($arr_chart_parameters[$str_key])) {
                $arr_thumbnail_parameters[$str_key] = $arr_chart_parameters[$str_key];
            }
        }

        return $arr_thumbnail_parameters;
    }

    //--------------------------------------------------------------------------

    protected function get_chart_parameters() {

        $arr_chart_parameters = array(
            self::PARAMETER_CHART_TYPE => $this->get_chart_code()
        );

        return $arr_chart_parameters;
    }

    //--------------------------------------------------------------------------

    public function merge_render_options($arr_options = array()) {
        $this->arr_render_options = array_merge($this->arr_render_options, $arr_options);
    }

    public function get_render_options() {
        return $this->arr_render_options;
    }

    //--------------------------------------------------------------------------

    public function publish_to_file($str_file, $arr_options = array()) {

        $arr_chart_parameters = $this->generate_chart_parameters($arr_options);

        $str_chart_source = $this->get_source($arr_chart_parameters, array_merge($arr_options, array(
            'use_local_source' => false
        )));

        $this->save_chart_image($str_chart_source, $str_file);

    }

    //--------------------------------------------------------------------------

    protected function encode_data($arr, $int_included_series = null) {
        return $this->encode_data_as_text($arr, $int_included_series);
    }

    final protected function encode_data_as_text($arr, $int_included_series = null) {

        $str_included_series = '';
        if(!empty($int_included_series) && ($int_included_series > 0)) {
            $str_included_series = $int_included_series;
        }

        return 't' . $str_included_series . ':' . implode('|', $arr);
    }

    public function export_for_serialise() {

        $arr_data = array(
            'width'            => $this->get_dynamic_width(),
            'height'           => $this->get_dynamic_height(),
            'headers'          => $this->get_headers(),
            'data'             => $this->get_data(),
            'series'           => $this->get_series(),
            'render_options'   => $this->get_render_options()
        );

        return $arr_data;
    }

    public function import_for_unserialise($arr_data) {

        if(isset($arr_data['headers'])) {
            $this->set_headers($arr_data['headers']);
        }

        if(isset($arr_data['data'])) {
            $this->set_data($arr_data['data']);
        }

        if(isset($arr_data['series'])) {
            $this->set_series($arr_data['series']);
        }

        if(isset($arr_data['width'])) {
            $this->set_dynamic_width($arr_data['width']);
        }

        if(isset($arr_data['height'])) {
            $this->set_dynamic_height($arr_data['height']);
        }

        if(isset($arr_data['render_options'])) {
            $this->merge_render_options($arr_data['render_options']);
        }

    }

    //--------------------------------------------------------------------------

    protected function convert_to_local_source($str_chart_source, $arr_options = array()) {

        $str_cache_key = md5($str_chart_source);

        $str_storage_path = self::get_configuration_option('storage_path');

        $str_chart_file = $str_cache_key . '.png';
        $str_image_file = $str_storage_path . DIRECTORY_SEPARATOR . $str_chart_file;

        if(!file_exists($str_image_file)) {
            $this->save_chart_image($str_chart_source, $str_image_file);
        }

        if(file_exists($str_image_file)) {
            $str_web_path = self::get_configuration_option('web_path');
            $str_chart_source = $str_web_path . DIRECTORY_SEPARATOR . $str_chart_file;
        } else {
            $str_chart_source = null;
        }

        return $str_chart_source;
    }

    protected function save_chart_image($str_chart_source, $str_image_file) {

        try {

            $arr_url = parse_url($str_chart_source);
            $str_query = str_replace('&amp;', '&', $arr_url['query']);
            parse_str($str_query, $arr_query);

            $str_url = substr($str_chart_source, 0, strpos($str_chart_source, '?'));

            $obj_client = new Zend_Http_Client($str_url);

            foreach($arr_query as $str_key => $str_value) {
                $obj_client->setParameterPost($str_key, $str_value);
            }

            // TODO: Set this to the user's client
            $obj_client->setConfig(array(
                'useragent' => 'Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.0.6) Gecko/2009020911 Ubuntu/8.10 (intrepid) Firefox/3.0.6'
            ));

            $obj_response = $obj_client->request('POST');

            if($obj_response->isSuccessful()) {
                file_put_contents($str_image_file, $obj_response->getBody());
            }

        } catch(Exception $obj_exception) {

        }

    }

    //--------------------------------------------------------------------------

    protected function convert_legend_position($enm) {

        $enm_output = null;

        switch($enm) {
            case Common_Chart::LEGEND_POSITION_TOP:
            case Common_Chart::LEGEND_POSITION_TOP_VERTICAL:
            case Common_Chart::LEGEND_POSITION_BOTTOM:
            case Common_Chart::LEGEND_POSITION_BOTTOM_VERTICAL:
            case Common_Chart::LEGEND_POSITION_RIGHT:
            case Common_Chart::LEGEND_POSITION_LEFT:
                $enm_output = $enm;
            break;
        }

        return $enm_output;
    }

    //--------------------------------------------------------------------------

    public function get_dynamic_width() {
        return $this->int_dynamic_width;
    }

    public function set_dynamic_width($int_width) {
        if($int_width > 0) {
            $this->int_dynamic_width = (int) $int_width;
        }
    }

    public function get_dynamic_height() {
        return $this->int_dynamic_height;
    }

    public function set_dynamic_height($int_height) {
        if($int_height > 0) {
            $this->int_dynamic_height = (int) $int_height;
        }
    }

    //--------------------------------------------------------------------------

    public function set_series($arr) {
        if(is_array($arr)) {
            $this->arr_series = $arr;
        }
    }

    public function get_series() {

        $arr_series = array();

        // only return series which are available (defined in data)
        foreach(array_keys($this->arr_data) as $str_series_key) {
            if(isset($this->arr_series[$str_series_key])) {
                $arr_series[$str_series_key] = $this->arr_series[$str_series_key];
            }
        }

        return $arr_series;
    }

    public function get_series_definitions($arr_options = array()) {

        $arr_series = $this->get_series();

        $arr_series_to_use_temp = array_keys($arr_series);
        if(isset($arr_options['use_series'])) {
            $arr_series_to_use_temp = (array) $arr_options['use_series'];
        }

        $arr_series_to_use = array();
        foreach($arr_series_to_use_temp as $str_series_key => $mix_meta) {

            if(is_string($mix_meta) || is_numeric($str_series_key)) {
                $str_series_key = $mix_meta;
                $arr_series_meta = array();
            } else if (is_array($mix_meta)) {
                $arr_series_meta = $mix_meta;
            }

            $arr_series_to_use[$str_series_key] = $arr_series_meta;

            if(
                !isset($arr_series_to_use[$str_series_key]['label']) &&
                isset($arr_series[$str_series_key])
            ) {
                $arr_series_to_use[$str_series_key]['label'] = $arr_series[$str_series_key];
            }
        }

        return $arr_series_to_use;
    }

    //--------------------------------------------------------------------------

    protected function set_data($arr) {
        if(is_array($arr)) {
            $this->arr_data = $arr;
        }
    }

    public function get_data() {
        return $this->arr_data;
    }

    protected function get_data_max() {
        return $this->num_data_max;
    }

    protected function get_data_min() {
        return $this->num_data_min;
    }

    protected function set_data_max($num) {
        $this->num_data_max = $num;
    }

    protected function set_data_min($num) {
        $this->num_data_min = $num;
    }

    //--------------------------------------------------------------------------

    public function get_headers() {
        return $this->arr_headers;
    }

    protected function set_headers($arr_headers) {
        $this->arr_headers = $arr_headers;
    }

    //--------------------------------------------------------------------------

    public function count_data_series() {
        return count($this->get_data());
    }

    protected function get_pow($int) {
        $int_pow = 0;
        $str_value = (string) $int;
        $int_length  = strlen($str_value);
        for($i = $int_length-1; $i >= 0; $i--) {
            if($str_value[$i] != 0) {
                break;
            }
            $int_pow++;
        }

        return $int_pow;
    }

    protected function append_parameter($arr_chart_parameters, $enm_parameter, $str_value) {

        if(isset($arr_chart_parameters[$enm_parameter])) {
            $str_value = '|' . $str_value;
        } else {
            $arr_chart_parameters[$enm_parameter] = '';
        }

        $arr_chart_parameters[$enm_parameter] .= $str_value;

        return $arr_chart_parameters;
    }

    protected function add_chart_fill($arr_chart_parameters, $str_value) {

        if(isset($arr_chart_parameters[self::PARAMETER_CHART_FILL])) {
            $str_value = '|' . $str_value;
        } else {
            $arr_chart_parameters[self::PARAMETER_CHART_FILL] = '';
        }
        
        $arr_chart_parameters[self::PARAMETER_CHART_FILL] .= $str_value;
        
        return $arr_chart_parameters;
    }

    protected function add_chart_marker($arr_chart_parameters, $str_value) {

        if(isset($arr_chart_parameters[self::PARAMETER_CHART_MARKERS])) {
            $str_value = '|' . $str_value;
        } else {
            $arr_chart_parameters[self::PARAMETER_CHART_MARKERS] = '';
        }

        $arr_chart_parameters[self::PARAMETER_CHART_MARKERS] .= $str_value;

        return $arr_chart_parameters;
    }

    protected function add_axis_range($arr_chart_parameters, $str_value) {

        if(isset($arr_chart_parameters[self::PARAMETER_CHART_AXIS_RANGE])) {
            $str_value = '|' . $str_value;
        } else {
            $arr_chart_parameters[self::PARAMETER_CHART_AXIS_RANGE] = '';
        }

        $arr_chart_parameters[self::PARAMETER_CHART_AXIS_RANGE] .= $str_value;

        return $arr_chart_parameters;
    }

    protected function add_axis_style($arr_chart_parameters, $str_value) {

        if(isset($arr_chart_parameters[self::PARAMETER_AXIS_STYLE])) {
            $str_value = '|' . $str_value;
        } else {
            $arr_chart_parameters[self::PARAMETER_AXIS_STYLE] = '';
        }

        $arr_chart_parameters[self::PARAMETER_AXIS_STYLE] .= $str_value;

        return $arr_chart_parameters;
    }

    static public function configure($arr_config) {
        self::$arr_config = $arr_config;
    }

    static public function get_configuration_option($str_key) {
        return isset(self::$arr_config[$str_key]) ? self::$arr_config[$str_key] : null;
    }

    public function convert_chart($enm_chart_type) {
        return null;
    }

}
